#include "errno.h"
#include "stdio.h"
#include "stdlib.h"

#include "fs/fs.h"
#include <sys/bus.h>

#include "spinor.h"

#include "mtd_common.h"

#include "host_common.h"
#include "los_base.h"
#include "mtd_dev.h"
#include "linux/kernel.h"
#include "los_bitmap.h"

/******************************************************************************
 * SPI FLASH elementray definition and function
 ******************************************************************************/

#define FLASH_PAGESIZE		256

#define NO_4B_ADDRESS_SUPPORT

/* Flash opcodes. */
#define OPCODE_WREN		6	/* Write enable */
#define OPCODE_WRDI		4	/* Write disable */
#define OPCODE_RDSR		5	/* Read status register */
#define OPCODE_WRSR		1	/* Write status register */
#define OPCODE_READ		3	/* Read data bytes */
#define OPCODE_PP		2	/* Page program */
#define OPCODE_SE		0xD8	/* Sector erase */
#define OPCODE_RES		0xAB	/* Read Electronic Signature */
#define OPCODE_RDID		0x9F	/* Read JEDEC ID */

#define OPCODE_FAST_READ	0x0B		/* Fast Read */
#define OPCODE_DOR			0x3B	/* Dual Output Read */
#define OPCODE_QOR			0x6B	/* Quad Output Read */
#define OPCODE_DIOR			0xBB	/* Dual IO High Performance Read */
#define OPCODE_QIOR			0xEB	/* Quad IO High Performance Read */
#define OPCODE_READ_ID		0x90	/* Read Manufacturer and Device ID */

#define OPCODE_P4E			0x20	/* 4KB Parameter Sectore Erase */
#define OPCODE_P8E			0x40	/* 8KB Parameter Sectore Erase */
#define OPCODE_BE			0x60	/* Bulk Erase */
#define OPCODE_BE1			0xC7	/* Bulk Erase */
#define OPCODE_QPP			0x32	/* Quad Page Programing */

#define OPCODE_CLSR			0x30
#define OPCODE_RCR			0x35	/* Read Configuration Register */

#define OPCODE_BRRD			0x16
#define OPCODE_BRWR			0x17

#define SPIC_READ_BYTES BIT(0)
#define SPIC_WRITE_BYTES BIT(1)
#define SPIC_4B_ADDR BIT(3)

#define SPI_XFER_BEGIN BIT(0)
#define SPI_XFER_END BIT(1)

#define SUNXI_SPI_FIFO_RF_CNT_MASK 0x7f
#define SUNXI_SPI_FIFO_RF_CNT_BITS 0
#define SUNXI_SPI_CTL_XCH BIT(31)

#define SUNXI_SPI_BURST_CNT(cnt) ((cnt) & 0xffffff)
#define SUNXI_SPI_XMIT_CNT(cnt) ((cnt) & 0xffffff)

#define SUNXI_SPI_CTL_CS_MASK 0x30
#define SUNXI_SPI_CTL_CS_LEVEL BIT(7)
#define SUNXI_SPI_CTL_CS(cs) (((cs) << 4) & SUNXI_SPI_CTL_CS_MASK)

#define SUNXI_SPI_CTL_ENABLE BIT(0)
#define SUNXI_SPI_CTL_MASTER BIT(1)
#define SUNXI_SPI_CTL_TP BIT(7)
#define SUNXI_SPI_CTL_SRST BIT(31)

#define SUNXI_SPI_CTL_CS_MANUAL BIT(6)
#define SUNXI_SPI_CTL_RF_RST BIT(15)
#define SUNXI_SPI_CTL_TF_RST BIT(31)


/* Status Register bits. */
#define SR_WIP			1	/* Write in progress */
#define SR_WEL			2	/* Write enable latch */
#define SR_BP0			4	/* Block protect 0 */
#define SR_BP1			8	/* Block protect 1 */
#define SR_BP2			0x10	/* Block protect 2 */
#define SR_EPE			0x20	/* Erase/Program error */
#define SR_SRWD			0x80	/* SR write protect */

struct MtdDev *spinor_mtd = NULL;
static struct spinor_info *spinor = NULL;
static struct spinor_host *host = NULL;

static int sunxispi_wait_ready(struct spinor_host *host, int sleep_ms);

extern void add_mtd_list(char *type, struct MtdDev *mtd);
extern int del_mtd_list(struct MtdDev *mtd);

struct chip_info {
	char		*name;
	uint8_t		id;
	uint32_t		jedec_id;
	unsigned long	sector_size;
	unsigned int	n_sectors;
	char		addr4b;
};

struct sunxi_spi_regs {
	uint32_t unused0[1];
	uint32_t glb_ctl;	/* 0x04 */
	uint32_t xfer_ctl;	/* 0x08 */
	uint32_t unused1[1];
	uint32_t int_ctl;	/* 0x10 */
	uint32_t int_sta;	/* 0x14 */
	uint32_t fifo_ctl;	/* 0x18 */
	uint32_t fifo_sta;	/* 0x1c */
	uint32_t wait;		/* 0x20 */
	uint32_t clk_ctl;	/* 0x24 */
	uint32_t unused2[2];
	uint32_t burst_cnt;	/* 0x30 */
	uint32_t xmit_cnt;	/* 0x34 */
	uint32_t burst_ctl;	/* 0x38 */
	uint32_t unused3[113];
	uint32_t tx_data;	/* 0x200 */
	uint32_t unused4[63];
	uint32_t rx_data;	/* 0x300 */
	
};
struct chip_info *spi_chip_info = NULL;

static struct chip_info chips_data [] = {
	/* REVISIT: fill in JEDEC ids, for parts that have them */
	{ "AT25DF321",          0x1f, 0x47000000, 64 * 1024, 64,  0 },
	{ "AT26DF161",          0x1f, 0x46000000, 64 * 1024, 32,  0 },
	{ "FL016AIF",           0x01, 0x02140000, 64 * 1024, 32,  0 },
	{ "FL064AIF",           0x01, 0x02160000, 64 * 1024, 128, 0 },
	{ "MX25L1605D",         0xc2, 0x2015c220, 64 * 1024, 32,  0 },//MX25L1606E
	{ "MX25L3205D",         0xc2, 0x2016c220, 64 * 1024, 64,  0 },//MX25L3233F
	{ "MX25L6405D",         0xc2, 0x2017c220, 64 * 1024, 128, 0 },//MX25L6433F
	{ "MX25L12805D",        0xc2, 0x2018c220, 64 * 1024, 256, 0 },//MX25L12835F
	{ "S25FL128P",          0x01, 0x20180301, 64 * 1024, 256, 0 },
	{ "S25FL129P",          0x01, 0x20184D01, 64 * 1024, 256, 0 },
	{ "S25FL164K",          0x01, 0x40170140, 64 * 1024, 128, 0 },
	{ "S25FL132K",          0x01, 0x40160140, 64 * 1024, 64,  0 },
	{ "S25FL032P",          0x01, 0x02154D00, 64 * 1024, 64,  0 },
	{ "S25FL064P",          0x01, 0x02164D00, 64 * 1024, 128, 0 },
	{ "S25FL116K",          0x01, 0x40150140, 64 * 1024, 32,  0 },
	{ "F25L64QA",           0x8c, 0x41170000, 64 * 1024, 128, 0 }, //ESMT
	{ "F25L32QA",           0x8c, 0x41168c41, 64 * 1024, 64,  0 }, //ESMT
	{ "EN25F16",            0x1c, 0x31151c31, 64 * 1024, 32,  0 },
	{ "EN25Q32B",           0x1c, 0x30161c30, 64 * 1024, 64,  0 },
	{ "EN25F32",            0x1c, 0x31161c31, 64 * 1024, 64,  0 },
	{ "EN25F64",            0x1c, 0x20171c20, 64 * 1024, 128, 0 },  // EN25P64
	{ "EN25Q64",            0x1c, 0x30171c30, 64 * 1024, 128, 0 },
	{ "W25Q32BV",           0xef, 0x40160000, 64 * 1024, 64,  0 },//W25Q32FV
	{ "W25X32VS",           0xef, 0x30160000, 64 * 1024, 64,  0 },
	{ "W25Q64BV",           0xef, 0x40170000, 64 * 1024, 128, 0 }, //S25FL064K //W25Q64FV
	{ "W25Q128BV",          0xef, 0x40180000, 64 * 1024, 256, 0 },//W25Q128FV
	{ "N25Q032A13ESE40F",   0x20, 0xba161000, 64 * 1024, 64,  0 },
	{ "N25Q064A13ESE40F",   0x20, 0xba171000, 64 * 1024, 128, 0 },
	{ "N25Q128A13ESE40F",   0x20, 0xba181000, 64 * 1024, 256, 0 },
	{ "GD25Q32B",           0xC8, 0x40160000, 64 * 1024, 64,  0 },
	{ "GD25Q64B",           0xC8, 0x40170000, 64 * 1024, 128, 0 },
	{ "GD25Q128C",          0xC8, 0x40180000, 64 * 1024, 256, 0 },
	{ "XT25F128",           0x0b, 0x40180b40, 64 * 1024, 256, 0 },

};

static void sunxi_spi_cs_activate(struct spinor_host *host, unsigned int cs)
{
	struct sunxi_spi_regs *regs;
	uint32_t reg;
	
	regs = (struct sunxi_spi_regs *)host->regbase;

	reg = readl(&regs->xfer_ctl);
	reg &= ~(SUNXI_SPI_CTL_CS_MASK | SUNXI_SPI_CTL_CS_LEVEL);
	reg |= SUNXI_SPI_CTL_CS(cs);
	writel(reg, &regs->xfer_ctl);
}

static void sunxi_spi_cs_deactivate(struct spinor_host *host, unsigned int cs)
{
	struct sunxi_spi_regs *regs;
	uint32_t reg;
	
	regs = (struct sunxi_spi_regs *)host->regbase;
	
	reg = readl(&regs->xfer_ctl);
	reg &= ~SUNXI_SPI_CTL_CS_MASK;
	reg |= SUNXI_SPI_CTL_CS_LEVEL;
	writel(reg, &regs->xfer_ctl);
}

static void sunxispi_hostwrite(struct spinor_host *host, const char *tx_buf,
	size_t nbytes)
{
	struct sunxi_spi_regs *regs;
	size_t i;
	char byte;

	regs = (struct sunxi_spi_regs *)host->regbase;
	
	if (!tx_buf)
		nbytes = 0;

	writel(SUNXI_SPI_XMIT_CNT(nbytes), &regs->xmit_cnt);
	writel(SUNXI_SPI_BURST_CNT(nbytes), &regs->burst_ctl);

	for (i = 0; i < nbytes; ++i) {
		byte = tx_buf ? *tx_buf++ : 0;
		writeb(byte, &regs->tx_data);
	}
}

static int sunxi_spi_xfer(struct spinor_host *host, unsigned int len,
	const void *dout, void *din, unsigned long flags)
{
	struct sunxi_spi_regs *regs;
	uint32_t reg;
	const char *tx_buf = dout;
	char *rx_buf = din;
	size_t i, nbytes;
	char byte;

	regs = (struct sunxi_spi_regs *)host->regbase;

	if (flags & SPI_XFER_BEGIN)
		sunxi_spi_cs_activate(host, 0);

	while (len) {
		nbytes = min(len, (size_t)64 - 1);

		writel(SUNXI_SPI_BURST_CNT(nbytes), &regs->burst_cnt);
		sunxispi_hostwrite(host, tx_buf, nbytes);
		reg = readl(&regs->xfer_ctl);	
		reg |= SUNXI_SPI_CTL_XCH;
		writel(reg, &regs->xfer_ctl);
		
		while (((readl(&regs->fifo_sta) &
			SUNXI_SPI_FIFO_RF_CNT_MASK) >>
			SUNXI_SPI_FIFO_RF_CNT_BITS) < nbytes);

		for (i = 0; i < nbytes; ++i) {
			byte = readb(&regs->rx_data);

			if (rx_buf)
				*rx_buf++ = byte;
		}

		len -= nbytes;

		if (tx_buf)
			tx_buf += nbytes;
	}

	if (flags & SPI_XFER_END)
		sunxi_spi_cs_deactivate(host, 0);
	return 0;
}

/*
 * read SPI flash device ID
 */
static int sunxispi_read_devid(struct spinor_host *host, uint8_t *rxbuf, int n_rx)
{
	uint8_t code = OPCODE_RDID;

	sunxi_spi_xfer(host, 1, &code, NULL, SPI_XFER_BEGIN);// send command
	sunxi_spi_xfer(host, n_rx, NULL, rxbuf, SPI_XFER_END); // read data

	return 0;
}

/*
 * read status register
 */
static int sunxispi_read_sr(struct spinor_host *host, uint8_t *val)
{
	uint8_t code = OPCODE_RDSR;

	sunxi_spi_xfer(host, 1, &code, NULL, SPI_XFER_BEGIN);// send command
	sunxi_spi_xfer(host, 1, NULL, val, SPI_XFER_END); // read data
	
	return 0;
}

/*
 * write status register
 */
static int sunxispi_write_sr(struct spinor_host *host, uint8_t *val)
{
	uint8_t code = OPCODE_WRSR;

	sunxi_spi_xfer(host, 1, &code, NULL, SPI_XFER_BEGIN);// send command
	sunxi_spi_xfer(host, 1, val, NULL, SPI_XFER_END); // read data
	return 0;
}


/*
 * Set write enable latch with Write Enable command.
 * Returns negative if error occurred.
 */
static inline int sunxispi_write_enable(struct spinor_host *host)
{
	uint8_t code = OPCODE_WREN;

	return sunxi_spi_xfer(host, 1, &code, NULL, SPI_XFER_BEGIN | SPI_XFER_END);	
}

static inline int sunxispi_write_disable(struct spinor_host *host)
{
	uint8_t code = OPCODE_WRDI;

	return sunxi_spi_xfer(host, 1, &code, NULL, SPI_XFER_BEGIN | SPI_XFER_END);
}

/*
 * Set all sectors (global) unprotected if they are protected.
 * Returns negative if error occurred.
 */
static inline int sunxispi_unprotect(struct spinor_host *host)
{
	uint8_t sr = 0;

	if (sunxispi_read_sr(host, &sr) < 0) {
		ERR_MSG("%s: read_sr fail: %x\n", __func__, sr);
		return -1;
	}

	if ((sr & (SR_BP0 | SR_BP1 | SR_BP2)) != 0) {
		sr = 0;
		sunxispi_write_sr(host, &sr);
	}
	
	return 0;
}

/*
 * Service routine to read status register until ready, or timeout occurs.
 * Returns non-zero if error.
 */
static int sunxispi_wait_ready(struct spinor_host *host, int sleep_ms)
{
	int count;
	int sr = 0;

	//udelay(1000 * sleep_ms);

	/* one chip guarantees max 5 msec wait here after page writes,
	 * but potentially three seconds (!) after page erase.
	 */

	for (count = 0;  count < ((sleep_ms+1) *1000); count++) {
		if ((sunxispi_read_sr(host, (uint8_t *)&sr)) < 0)
			break;
		else if (!(sr & SR_WIP)) {
			return 0;
		}

		LOS_Udelay(500);
		/* REVISIT sometimes sleeping would be best */
	}


	return -1;
}

/*
 * Erase one sector of flash memory at offset ``offset'' which is any
 * address within the sector which should be erased.
 *
 * Returns 0 if successful, non-zero otherwise.
 */
static int sunxispi_erase_sector(struct spinor_host *host, uint32_t offset)
{
	/* Wait until finished previous write command. */
	uint8_t cmd[5];
	
	cmd[0] = OPCODE_SE;
	
	if (sunxispi_wait_ready(host, 3))
		return -1;

	/* Send write enable, then erase commands. */
	sunxispi_write_enable(host);
	sunxispi_unprotect(host);

	{
		cmd[1] = offset >> 16;
		cmd[2] = offset >> 8;
		cmd[3] = offset; 
		sunxi_spi_xfer(host, 4, cmd, NULL, SPI_XFER_BEGIN | SPI_XFER_END);
		sunxispi_wait_ready(host, 950);
	}


	sunxispi_write_disable(host);

	return 0;
}

struct chip_info *chip_prob(struct spinor_host *host)
{
	struct chip_info *info, *match;
	uint8_t buf[5];
	uint32_t jedec, weight;
	int i;
	
	//memset(buf, 0, 5);

	sunxispi_read_devid(host, buf, 5);
	jedec = (uint32_t)((uint32_t)(buf[1] << 24) | ((uint32_t)buf[2] << 16) | ((uint32_t)buf[3] <<8) | (uint32_t)buf[4]);

	PRINT_RELEASE("spi device id: (%x)\n", jedec);

	// FIXME, assign default as AT25D
	weight = 0xffffffff;
	match = &chips_data[0];
	for (i = 0; i < sizeof(chips_data)/sizeof(chips_data[0]); i++) {
		info = &chips_data[i];
		if (info->id == buf[0]) {
			if (info->jedec_id == jedec) {
				PRINT_RELEASE("find flash: %s\n", info->name);
				return info;
			}

			if (weight > (info->jedec_id ^ jedec)) {
				weight = info->jedec_id ^ jedec;
				match = info;
			}
		}
	}

	return match;
}

static int sunxi_spi_erase(struct spinor_info *spinor, uint32_t offset, uint32_t len){
	while (len) {
        if (sunxispi_erase_sector(host, offset)) {
            return -EIO;
        }
        offset += spinor->dev.blocksize;
        len -= spinor->dev.blocksize;
    }
	return offset;
}

static int sunxi_spi_write(struct spinor_info *spinor, uint32_t to, uint32_t len,
						const char *buf)
{
	uint32_t page_offset, page_size;
	int retlen = 0;
	uint8_t cmd[5];

	/* sanity checks */
	if (len == 0)
		return 0 ;
	if (to + len > spi_chip_info->sector_size * spi_chip_info->n_sectors)
		return -1;

	/* Wait until finished previous write command. */
	if (sunxispi_wait_ready(host, 2)) {
		return -1;
	}

	/* Set up the opcode in the write buffer. */
	cmd[0] = OPCODE_PP;

	{
		cmd[1] = to >> 16;
		cmd[2] = to >> 8;
		cmd[3] = to;
	}

	/* what page do we start with? */
	page_offset = to % FLASH_PAGESIZE;


	/* write everything in PAGESIZE chunks */
	while (len > 0) {
		page_size = min(len, FLASH_PAGESIZE - page_offset);
		page_offset = 0;
		/* write the next page to flash */
		{
			cmd[1] = to >> 16;
			cmd[2] = to >> 8;
			cmd[3] = to;
		}

		sunxispi_wait_ready(host, 3);
		sunxispi_write_enable(host);
		sunxispi_unprotect(host);

		sunxi_spi_xfer(host, 4, cmd, NULL, SPI_XFER_BEGIN);
		sunxi_spi_xfer(host, page_size, buf, NULL, SPI_XFER_END);

		
		retlen += len;
		
		len -= page_size;
		to += page_size;
		buf += page_size;
	}

	sunxispi_write_disable(host);
	return retlen;
}

static int sunxi_spi_read(struct spinor_info *spinor, uint32_t from,
						uint32_t len, const char *buf)
{
	uint8_t cmd[5];
	/* sanity checks */
	if (len == 0)
		return 0;

	/* Wait till previous write/erase is done. */
	if (sunxispi_wait_ready(host, 3)) {
		/* REVISIT status return?? */
		return -1;
	}

	/* NOTE: OPCODE_FAST_READ (if available) is faster... */

	//start_time = get_timer(0);

	cmd[0] = OPCODE_FAST_READ;
	{
		cmd[1] = from >> 16;
		cmd[2] = from >> 8;
		cmd[3] = from;
		sunxi_spi_xfer(host, 5, 	cmd, 	NULL, SPI_XFER_BEGIN);
		sunxi_spi_xfer(host, len, 	NULL,	(char *)buf, SPI_XFER_END);
	}

	return len;
}

static void sunxi_spi_readid(struct spinor_info *info, char *id){
	sunxispi_read_devid(host, (uint8_t *)id, 6);
}

static void sunxi_spi_idsprobe(struct spinor_info *info)
{
	spi_chip_info = chip_prob(host);
	return;
}


static void sunxi_spinor_init(struct spinor_info *spinor)
{
	spinor->erase = sunxi_spi_erase;
	spinor->write = sunxi_spi_write;
	spinor->read = sunxi_spi_read;
	
	spinor->read_id = sunxi_spi_readid;
	spinor->ids_probe = sunxi_spi_idsprobe;
	
	spinor->cur_cs = 0;
}

static int sunxi_spi_host_init(struct spinor_host *host)
{
	struct sunxi_spi_regs *regs;
	uint32_t reg;
	regs = (struct sunxi_spi_regs *)host->regbase;
	
	reg = readl(&regs->glb_ctl);
	reg |= (SUNXI_SPI_CTL_MASTER | SUNXI_SPI_CTL_ENABLE | SUNXI_SPI_CTL_TP | SUNXI_SPI_CTL_SRST);
	writel(reg, &regs->glb_ctl);
	
	while (readl(&regs->glb_ctl) & SUNXI_SPI_CTL_SRST);
	
	reg = readl(&regs->xfer_ctl);
	reg |= (SUNXI_SPI_CTL_CS_MANUAL | SUNXI_SPI_CTL_CS_LEVEL);
	writel(reg, &regs->xfer_ctl);
	
	reg = readl(&regs->fifo_ctl);
	reg |= (SUNXI_SPI_CTL_RF_RST | SUNXI_SPI_CTL_TF_RST);
	writel(reg, &regs->fifo_ctl);
	
	return 0;
}

static int spinor_host_init(struct spinor_host *host)
{
    sunxi_spinor_init(host->spinor);
	sunxi_spi_host_init(host);

    return 0;
}

static int sunxi_spi_probe(device_t self)
{
    return (BUS_PROBE_DEFAULT);
}

extern int spinor_node_register(struct MtdDev *mtd);

static int sunxi_spi_attach(device_t self)
{
	struct resource *res = NULL;
    int unit = device_get_unit(self);
    spinor_mtd = (struct MtdDev *)zalloc(sizeof(struct MtdDev));

    if (!spinor_mtd) {
        ERR_MSG("no mem for mtd_info!\n");
        return -1;
    }
    spinor = (struct spinor_info *)zalloc(sizeof(struct spinor_info));
    if (!spinor) {
        ERR_MSG("no mem for spinor_info!\n");
        goto err;
    }
    spinor_mtd->priv = spinor;
    host = (struct spinor_host *)zalloc(sizeof(struct spinor_host));
    if (!host) {
        ERR_MSG("no mem for spinor_host!\n");
        goto err1;
    }
    host->spinor = spinor;
    res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &unit, 0);
    if (!res) {
        goto err2;
    }
    host->regbase = (char *)ioremap(res->start, res->count);
    if (!host->regbase) {
        goto err2;
    }
    spinor->priv = host;
    /* spinor host init */
    if (spinor_host_init(host)) {
        ERR_MSG("spinor host init fail!\n");
        goto err2;
    }

    spi_chip_info = chip_prob(host);
	spinor->dev.chipsize = spi_chip_info->sector_size * spi_chip_info->n_sectors;
	spinor->dev.blocksize = spi_chip_info->sector_size;
	
	extern void spinor_register(struct MtdDev *mtd);
    /* spinor register */
    spinor_register(spinor_mtd);
	
    add_mtd_list("spinor", spinor_mtd);
    if (spinor_node_register(spinor_mtd)) {
        ERR_MSG("spinor node register fail!\n");
        goto err2;
    }
    return 0;

err2:
    iounmap((void *)host->regbase);
    free(host);
    host = NULL;
err1:
    free(spinor);
    spinor = NULL;
err:
    free(spinor_mtd);
    spinor_mtd = NULL;
	dprintf("%s: return -1\n", __func__);
    return -1;
}

struct MtdDev *GetMtd(const char *type)
{
    return spinor_mtd;
}

static int sunxi_spi_detach(device_t self)
{
    if (del_mtd_list(spinor_mtd)) {
        ERR_MSG("remove spinor fail!\n");
        return -1;
    } else {
        iounmap((void *)host->regbase);
        free(host);
        host = NULL;
        free(spinor);
        spinor = NULL;
        free(spinor_mtd);
        spinor_mtd = NULL;
    }
    return 0;
}

static device_method_t sunxi_spi_methods[] = {
    /* Device interface */
    DEVMETHOD(device_probe, sunxi_spi_probe),
    DEVMETHOD(device_attach, sunxi_spi_attach),
    DEVMETHOD(device_detach, sunxi_spi_detach),
    DEVMETHOD_END
};

static driver_t sunxi_spi_driver = {
    .name = "sunxi_spi",
    .methods = sunxi_spi_methods,
    .size = 1,
};

static devclass_t sunxi_spi_devclass;
DRIVER_MODULE(sunxi_spi, nexus, sunxi_spi_driver, sunxi_spi_devclass, 0, 0);

int sunxi_spi_init(void){
	return driver_module_handler(NULL, MOD_LOAD, &sunxi_spi_nexus_driver_mod);
}
